package com.captjack.distributedlock.lock.zookeeper;

import com.captjack.distributedlock.lock.ZookeeperDistributedLock;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;

import java.util.concurrent.TimeUnit;

import static com.captjack.distributedlock.lock.zookeeper.ZookeeperDistributedLockConstant.INTER_PROCESS_LOCK_MAP;

/**
 * zookeeper锁实现
 * ZooKeeper节点类型： <br>
 * ZooKeeper 节点是有生命周期的，这取决于节点的类型。<br>
 * 在 ZooKeeper 中，节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)，以及时序节点(SEQUENTIAL)<br>
 * 具体在节点创建过程中，一般是组合使用，可以生成以下 4种节点类型<br>
 * <p>
 * 持久节点（PERSISTENT） <br>
 * 所谓持久节点，是指在节点创建后，就一直存在，直到有删除操作来主动清除这个节点。 <br>
 * (该节点不会因为创建该节点的客户端会话失效而消失)
 * <p>
 * 临时节点（EPHEMERAL） <br>
 * 和持久节点不同的是，临时节点的生命周期和客户端会话绑定。<br>
 * 如果客户端会话失效，那么这个节点就会自动被清除掉。<br>
 * (这里提到的是会话失效，而非连接断开)<br>
 * 另外，在临时节点下面不能创建子节点。<br>
 * <p>
 * 持久顺序节点（PERSISTENT_SEQUENTIAL） <br>
 * 这类节点的基本特性和上面的持久节点类型是一致的。<br>
 * 额外的特性是，在ZK中，每个父节点会为他的第一级子节点维护一份时序，会记录每个子节点创建的先后顺序。 <br>
 * 基于这个特性，在创建子节点的时候，可以设置这个属性，那么在创建节点过程中，ZK会自动为给定节点名加上一个数字后缀，作为新的节点名。这个数字后缀的范围是整型的最大值。<br>
 * 假如: 我们在/lock/目录下创建节3个点，集群会按照提起创建的顺序来创建节点，节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003。
 * <p>
 * 临时顺序节点（EPHEMERAL_SEQUENTIAL） <br>
 * 具有临时节点和顺序节点的特性。我们可以利用这个特性来实现分布式锁。 <br>
 * 基于zookeeper瞬时有序节点实现的分布式锁，其主要逻辑如下： <br>
 * 客户端对某个功能加锁时，在zookeeper上的与该功能对应的指定节点的目录下，生成一个唯一的瞬时有序节点。 <br>
 * 判断是否获取锁的方式，只需要判断有序节点中序号最小的一个，如果最小的节点与当客户端记录节点号相同获得锁<br>
 * 当释放锁的时候，只需将这个瞬时节点删除即可。 <br>
 * <p>
 * Curator是Netflix公司开源的一个Zookeeper客户端，提供了一些操作Zookeeper的方法，其中包括创建分布式锁 <br><br> *<br> *
 *
 * @author Jack Sparrow
 * @version 1.0.0
 * @date 2018/7/12 21:55
 * package com.captjack.distributedlock.lock.zookeeper
 */
public class ZookeeperDistributedLockImpl implements ZookeeperDistributedLock {

    /**
     * curatorFramework zookeeper操作客户端
     */
    private final CuratorFramework curatorFramework;

    @Override
    public boolean acquire(String lockId, long waitLockTime, TimeUnit waitLockTimeUnit, boolean isWaitLockUntilTimeOut) throws Exception {
        // 尝试加锁，acquire是阻塞操作，一直等到获取到锁或者超时为止
        InterProcessLock interProcessLock = getLockTool(lockId);
        if (isWaitLockUntilTimeOut) {
            return interProcessLock.acquire(waitLockTime, waitLockTimeUnit);
        } else {
            interProcessLock.acquire();
            return true;
        }
    }

    @Override
    public boolean release(String lockId) throws Exception {
        // 解锁
        getLockTool(lockId).release();
        return true;
    }

    /**
     * 根据锁的id获取锁操作工具
     *
     * @param lockId 锁id
     * @return InterProcessSemaphoreMutex不可重入锁
     */
    private InterProcessLock getLockTool(String lockId) {
        InterProcessLock interProcessLock;
        if (INTER_PROCESS_LOCK_MAP.containsKey(lockId)) {
            interProcessLock = INTER_PROCESS_LOCK_MAP.get(lockId);
        } else {
            interProcessLock = new InterProcessSemaphoreMutex(curatorFramework, lockId);
            // 加入map中
            INTER_PROCESS_LOCK_MAP.put(lockId, interProcessLock);
        }
        return interProcessLock;
    }

    public ZookeeperDistributedLockImpl(CuratorFramework curatorFramework) {
        this.curatorFramework = curatorFramework;
        // 开启
        if (!this.curatorFramework.getState().equals(CuratorFrameworkState.STARTED)) {
            this.curatorFramework.start();
        }
    }

}
